package com.huawei.spare.part.replace.framework;


import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import com.google.common.base.Strings;
import com.huawei.spare.part.replace.Constants;
import com.huawei.spare.part.replace.models.*;
import com.huawei.spare.part.replace.models.constants.Plane;
import com.huawei.spare.part.replace.utils.Application;
import com.huawei.spare.part.replace.utils.JsonMapper;
import com.huawei.spare.part.replace.utils.ZipUtils;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import net.lingala.zip4j.exception.ZipException;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public enum SmartDatabase {

    INSTANCE;

    private static Logger logger = LoggerFactory.getLogger(SmartDatabase.class);
    private DbMeta meta;
    private List<Switch> switches;
    private List<Solution> solutions;
    private List<SerialNo> serialNoList;
    private List<InstallPackage> installPackages;
    private List<SwitchMatchedPackage> switchMatchedPackages;

    public static SmartDatabase get() {
        return SmartDatabase.INSTANCE;
    }

    public DbMeta meta(boolean reload) {
        if (reload) {
            this.reloadDbMeta();
        }
        return this.meta;
    }

    public void importDb(File file) throws ZipException {
        final String databaseFolderPath = Application.getDatabaseFolder();
        final File folder = new File(databaseFolderPath);
        if (folder.exists()) {
            folder.delete();
        }

        folder.mkdirs();
        ZipUtils.unzip(file, databaseFolderPath);
    }


    /**
     * 重新加载数据
     *
     * @throws IOException
     */
    public boolean reload() throws IOException {
        this.reloadDbMeta();
        if (this.meta != null && !Strings.isNullOrEmpty(this.meta.getVersion())) {
            this.reloadSwitchTable();
            this.reloadSolutions();
            this.reloadSerialNoTable();
            this.reloadSwitchVersionMatch();
            this.reloadInstallPackages();
            return true;
        }
        return false;
    }

    public void reloadDbMeta() {
        final File dbMetaFile = new File(Application.getDbMetaFile());
        try {
            if (dbMetaFile.exists()) {
                String fileContent = FileUtils.readFileToString(dbMetaFile, Constants.UTF8);
                this.meta = JsonMapper.nonDefaultMapper().getBean(fileContent, DbMeta.class);
            } else {
                this.meta = new DbMeta();
            }
        } catch (IOException e) {
            this.meta = new DbMeta();
            logger.error("Failed to load db meta", e);
        }
    }

    public void reloadSerialNoTable() {
        logger.info("Start loading Serial-No table from excel");
        ImportParams params = new ImportParams();
        String filepath = Application.getDbTableFile(Constants.DbFileSerialNo);
        final List<SerialNo> list = ExcelImportUtil.importExcel(new File(filepath), SerialNo.class, params);
        this.serialNoList = list.stream().filter(s -> !Strings.isNullOrEmpty(s.getSn())).collect(Collectors.toList());
        logger.info("Parse Serial-No Excel done, record size: {}", this.serialNoList.size());
    }

    public void reloadSolutions() throws IOException {
        logger.info("Start loading solution table from excel");
        ImportParams params = new ImportParams();
        String filepath = Application.getDbTableFile(Constants.DbFileSolution);
        final String content = FileUtils.readFileToString(new File(filepath), Constants.UTF8);
        solutions = JsonMapper.nonEmptyMapper().getListBean(content, Solution.class);
        logger.info("Parse solution done, record size: {}", solutions.size());
    }


    /**
     * 获取备件升级方案
     *
     * @param project
     * @param reload
     * @return
     * @throws IOException
     */
    public Optional<Solution> getReplaceSolution(Project project, boolean reload)
            throws IOException {
        if (this.solutions == null || reload) {
            this.reloadSolutions();
        }

        final Switch switch0 = project.getSwitch0();
        final Project.Defective defective = project.getDefective();
        final Project.Spare spare = project.getSpare();
        return solutions.stream()
                        .filter(s -> s.getModel().contains(switch0.getBoardType()))
                        .filter(s -> s.getPlanes().containsAll(switch0.getPlanes()))
                        .filter(s -> defective.getIsV5().equals(s.getDefective().getIsV5()))
                        .filter(s -> spare.getIsV5().equals(s.getSpare().getIsV5()))
                        // 当solution中isStacked配置了true，要求坏件也必须是 isStacked
                        // 当solution中isStacked配置了false，不对坏件换件做要求
                        .filter(s -> s.getIsStacked() ? defective.getIsStacked() : true)
                        .filter(s -> s.getDefective().containsSoftwareVersion(defective.getSoftwareVersion()))
                        .filter(s -> s.getDefective().containsCpldVersion(defective.getCpldVersion()))
                        .filter(s -> s.getSpare().containsSoftwareVersion(spare.getSoftwareVersion()))
                        .filter(s -> s.getSpare().containsCpldVersion(spare.getCpldVersion()))
                        .findFirst();
    }

    public void reloadSwitchTable() {
        logger.info("Start loading Switch table from excel");
        ImportParams params = new ImportParams();
        String filepath = Application.getDbTableFile(Constants.DbFileSwitch);
        final List<Switch> list = ExcelImportUtil.importExcel(new File(filepath), Switch.class, params);
        this.switches = list.stream().filter(s -> !Strings.isNullOrEmpty(s.getBoardType())).collect(Collectors.toList());
        logger.info("Parse Serial-No Excel done, record size: {}", this.switches.size());
    }

    public void reloadSwitchVersionMatch() {
        logger.info("Start loading Switch version match table from excel");
        ImportParams params = new ImportParams();
        params.setStartSheetIndex(0);
        params.setSheetNum(8);
        String filepath = Application.getDbTableFile(Constants.DbFileVersionMatch);
        final List<SwitchMatchedPackage> list = ExcelImportUtil.importExcel(new File(filepath), SwitchMatchedPackage.class, params);
        switchMatchedPackages = list.stream().filter(s -> s.getBoardTypeList().size() > 0).collect(Collectors.toList());
        logger.info("Parse Switch version match Excel done, record size: {}", switchMatchedPackages.size());
    }

    public void reloadInstallPackages() {
        logger.info("Start loading install package table from excel");
        ImportParams params = new ImportParams();
        String filepath = Application.getDbTableFile(Constants.DbFileInstallPackages);
        final List<InstallPackage> list = ExcelImportUtil.importExcel(new File(filepath), InstallPackage.class, params);
        installPackages = list.stream().filter(s -> s.getBoardTypes().size() > 0).collect(Collectors.toList());
        logger.info("Parse install package Excel done, record size: {}", installPackages.size());
    }

    public ObservableList<String> getSwitchBordTypes() {
        if (this.switchMatchedPackages == null) {
            this.reloadSwitchVersionMatch();
        }
        return FXCollections.observableArrayList(this.switchMatchedPackages.stream()
                                                                           .flatMap(i -> i.getBoardTypeList().stream()).distinct()
                                                                           .sorted()
                                                                           .collect(Collectors.toList()));
    }

    public List<SwitchMatchedPackage> getSwitchMatchedPackage(String boardType) {
        if (this.switchMatchedPackages == null) {
            this.reloadSwitchVersionMatch();
        }
        return this.switchMatchedPackages.stream().filter(item -> item.getBoardTypeList().contains(boardType))
                                         .collect(Collectors.toList());
    }

    /**
     * 通过单板类型和软件版本号，查找单板版本匹配信息
     *
     * @param boardType
     * @param softwareVersion
     * @return
     */
    public SwitchMatchedPackage findSwitchMatchedPackage(String boardType, String softwareVersion) {
        if (this.switchMatchedPackages == null) {
            this.reloadSwitchVersionMatch();
        }
        return this.switchMatchedPackages.stream().filter(item -> {
            return item.getBoardTypeList().contains(boardType) && softwareVersion.equals(item.getSoftwareVersion());
        }).findFirst().get();
    }


    /**
     * get Serial Number record by sn
     *
     * @param sn
     * @param boardType
     * @param reload
     * @return
     */
    public SerialNo getSn(String sn, String boardType, boolean reload) {
        if (this.serialNoList == null || reload) {
            this.reloadSerialNoTable();
        }
        return this.serialNoList.stream().filter(i -> {
            return !Strings.isNullOrEmpty(i.getSn()) && i.getSn().equalsIgnoreCase(sn);
        }).findFirst().get();
    }


    /**
     * 通过单板类型查找Switch信息
     *
     * @param boardType
     * @param reload
     * @return
     */
    public Switch getSwitchByBoardType(String boardType, Boolean reload) {
        if (this.switches == null || reload) {
            this.reloadSwitchTable();
        }
        return this.switches.stream().filter(s -> boardType.equalsIgnoreCase(s.getBoardType())).findFirst().get();
    }

    /**
     * 查找合适的平面软件包
     *
     * @param boardType
     * @param plane
     * @param version
     * @return
     */
    public Optional<InstallPackage> getInstallPackage(String boardType, Plane plane, String version) {
        final Optional<InstallPackage> fabricPackage = this.installPackages
                .stream()
                .filter(p -> p.getBoardTypes().contains(boardType))
                .filter(p -> plane.equals(p.getPlane()))
                .filter(p -> version.equalsIgnoreCase(p.getSoftwareVersion()))
                .findFirst();
        return fabricPackage;
    }

    /**
     * 查找合适的CPLD软件包
     *
     * @param boardType
     * @param plane
     * @param cpldVersion
     * @return
     */
    public Optional<InstallPackage> getCpldInstallPackage(String boardType, Plane plane, String cpldVersion) {
        final Optional<InstallPackage> cpldPackage = this.installPackages
                .stream()
                .filter(p -> p.getBoardTypes().contains(boardType))
                .filter(p -> plane.equals(p.getPlane()))
                .filter(p -> !Strings.isNullOrEmpty(p.getCpldVersion()))
                .filter(p -> cpldVersion.equalsIgnoreCase(p.getCpldVersion()) )
                .findFirst();
        return cpldPackage;
    }

    /**
     * 查找合适的CPLD软件包
     *
     * @param boardType
     * @param plane
     * @return
     */
    public Optional<InstallPackage> getBmcInstallPackage(String boardType, Plane plane, String bmcVersion) {
        final Optional<InstallPackage> bmcPackage = this.installPackages
                .stream()
                .filter(p -> p.getBoardTypes().contains(boardType))
                .filter(p -> plane.equals(p.getPlane()))
                .filter(p -> bmcVersion.equalsIgnoreCase(p.getBmcVersion()))
                .findFirst();
        return bmcPackage;
    }


}
